﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using DiagramCreator.Arrows;
using System.Collections.ObjectModel;

namespace DiagramCreator.Nodes
{
    /// <summary>
    /// Interaction logic for GenericNode.xaml
    /// </summary>
    public abstract partial class GenericNode : UserControl, IDisposable
    {
        #region - Konstruktor -

        public GenericNode()
        {
            InitializeComponent();
            this.NewActionNode.MouseDown += new MouseButtonEventHandler(NewActionNode_MouseDown);
            this.NewdecissionNode.MouseDown += new MouseButtonEventHandler(NewdecissionNode_MouseDown);
            this.NewNoteNode.MouseDown += new MouseButtonEventHandler(NewNoteNode_MouseDown);
            this.StopNode.MouseDown += new MouseButtonEventHandler(StopNode_MouseDown);
            this.StopNodeInner.MouseDown += new MouseButtonEventHandler(StopNodeInner_MouseDown);
            this.NodeTextField.GotFocus += new RoutedEventHandler(NodeTextField_GotFocus);
            this.NodeTextField.LostFocus += new RoutedEventHandler(NodeTextField_LostFocus);
            this.NodeTextField.MouseDown += new MouseButtonEventHandler(NodeTextField_MouseDown);
            this.NodeTextField.MouseUp += new MouseButtonEventHandler(NodeTextField_MouseUp);
            this.BorderRect.MouseDown += new MouseButtonEventHandler(BorderRect_MouseDown);
            this.BorderRect.MouseUp += new MouseButtonEventHandler(BorderRect_MouseUp);
            this.BorderRect.MouseLeave += new MouseEventHandler(BorderRect_MouseLeave);
            this.BorderRect.MouseEnter += new MouseEventHandler(BorderRect_MouseEnter);
            this.MouseMove += new MouseEventHandler(GenericNode_MouseMove);
            this.MinWidth = 5.0;
            this.MinHeight = 5.0;
            this.NodeTextField.Focusable = false;
            this.NodeTextField.MouseDoubleClick += new MouseButtonEventHandler(NodeTextField_MouseDoubleClick);
            CanHaveMultipleChildren = true;
            ChildNodes.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ChildNodes_CollectionChanged);
        }

        void ChildNodes_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            // obsługa zmiany ilości dzieci
            if (ChildNodes.Count - ChildNodes.OfType<NoteNode>().Count() > 0 && !CanHaveMultipleChildren)
            {
                NewActionNode.Visibility = Visibility.Hidden;
                NewdecissionNode.Visibility = Visibility.Hidden;
                StopNode.Visibility = Visibility.Hidden;
                StopNodeInner.Visibility = Visibility.Hidden;
            }
            else
            {
                NewActionNode.Visibility = Visibility.Visible;
                NewdecissionNode.Visibility = Visibility.Visible;
                StopNode.Visibility = Visibility.Visible;
                StopNodeInner.Visibility = Visibility.Visible;
            }
        }
        

        void StopNodeInner_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!isDragging)
            {
                isDragging = true;
            }
            if (ChildControlBeginDrag != null)
            {
                ChildControlBeginDrag(this, NODE_TYPE.END_POINT);
            }
        }

        void StopNode_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!isDragging)
            {
                isDragging = true;
            }
            if (ChildControlBeginDrag != null)
            {
                ChildControlBeginDrag(this, NODE_TYPE.END_POINT);
            }
        }

        Cursor backup = Cursors.Arrow;

        void NodeTextField_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (ShapeMouseUp != null && singleClick)
            {
                singleClick = false;
                Cursor = backup;
                ShapeMouseUp(this, e);
            }
        }

        bool singleClick = false;

        void NodeTextField_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (ShapeMouseDown != null && e.ClickCount == 1)
            {
                singleClick = true;
                backup = Cursor;
                Cursor = Cursors.SizeNWSE;
                NodeTextField.Focusable = false;
                ShapeMouseDown(this, e);
            }
            else
            {
                singleClick = false;
            }
        }

        void NewNoteNode_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!isDragging)
            {
                isDragging = true;
            }
            if (ChildControlBeginDrag != null)
            {
                ChildControlBeginDrag(this, NODE_TYPE.NOTE_NODE);
            }
        }

        #endregion

        #region - CanHaveNodeTextField -

        public bool CanHaveNodeTextField
        {
            set
            {
                if (value)
                {
                    NodeTextField.Visibility = Visibility.Hidden;
                    //NodeTextField.IsEnabled = true;
                }
                else
                {
                    NodeTextField.Visibility = Visibility.Visible;
                    //NodeTextField.IsEnabled = false;
                }
            }
        }

        #endregion

        #region - NodeTextField_MouseDoubleClick -

        void NodeTextField_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            this.NodeTextField.Focusable = true;
            this.NodeTextField.Focus();
        }

        #endregion

        private bool isDragging = false;
        double mouseDownX, mouseDownY;
        double tmpWidth, tmpHeight;

        private Cursor prevCursor = Cursors.Arrow;
        private bool isInResizeMode = false;    

        #region - IsSizeable -
        bool isSizeable = true;
        public bool IsSizeable
        {
            get { return isSizeable; }
            set { isSizeable = value; }
        }

        #endregion

        #region - FocusLostElement -

        private UIElement focusLostElement = null; // element który uzyskuje focus, aby bieżący nod go stracił
        public UIElement FocusLostElement
        {
            set
            {
                focusLostElement = value;
            }
        }

        #endregion

        #region - GetCrossingPoint -
        /// <summary>
        /// metoda ma wskazac punkt zetkniecia linii - łaczącej inny kształ z danym, z danym kształtem :)
        /// </summary>
        /// <param name="from"></param>
        /// <returns></returns>
        public virtual Point GetCrossingPoint(Point from)
        {
            double negativeLineRatio = -1 * ShapeGridHeight / ShapeGridWidth;
            double lineRatio = ShapeGridHeight / ShapeGridWidth;
            double fromRatio = (MiddleY- from.Y) / (MiddleX - from.X);

            if (Math.Abs(fromRatio) >= Math.Abs(negativeLineRatio))
            {
                if (from.Y < MiddleY)
                {
                    return new Point(MiddleX, Top);
                }
                else
                {
                    return new Point(MiddleX, Bottom);
                }
            }
            else
            {
                if (from.X < MiddleX)
                {
                    return new Point(Left, MiddleY);
                }
                else
                {
                    return new Point(Right, MiddleY);
                }
            }
        }

        #endregion

        #region - CanHaveMultipleChildren -

        public bool CanHaveMultipleChildren { get; set; }

        #endregion

        #region - Selected -

        private bool isSelected = false;
        public bool Selected
        {
            set
            {
                isSelected = value;
                if (isSelected)
                {
                    drawBorder();
                }
                else
                {
                    clearBorder();
                }
            }
            get
            {
                return isSelected;
            }
        }

        #endregion

        #region - drawBorder clearBorder -

        void drawBorder()
        {
            this.BorderRect.Visibility = Visibility.Visible;
        }

        void clearBorder()
        {
            this.BorderRect.Visibility = Visibility.Hidden;
        }

        #endregion

        #region - ChildNodes -

        private ObservableCollection<GenericNode> childNodes = new ObservableCollection<GenericNode>();
        public ObservableCollection<GenericNode> ChildNodes
        {
            get
            {
                return childNodes;
            }            
        }

        public void AddChildNode(GenericNode node)
        {
            childNodes.Add(node);
        }

        public void RemoveChildNode(GenericNode node)
        {
            childNodes.Remove(node);
        }

        #endregion

        #region - ConnectionLines -

        private Dictionary<GenericNode, GenericConnectionLine> connectionLines = new Dictionary<GenericNode, GenericConnectionLine>();
        public Dictionary<GenericNode, GenericConnectionLine> ConnectionLines
        {
            get
            {
                return connectionLines;
            }
            set
            {
                connectionLines = value;
                if (connectionLines != null)
                {
                    if (connectionLines.Count > 1)
                    {
                        throw new Exception("Too many connection lines. Only one allowed.");
                    }
                    foreach (GenericConnectionLine line in connectionLines.Values)
                    {
                        line.updatePosition();
                    }
                }
            }
        }

        #endregion

        #region - ParentNode -

        public List<GenericNode> ParentNodes { get; set; }

        #endregion

        #region - UpdateConnectionLines -

        /// <summary>
        /// Wykoanuje aktualizacje polozenia linii laczacych
        /// ten Node z innymi
        /// </summary>
        public void UpdateConnectionLines()
        {
            if (ConnectionLines != null)
            {
                foreach (GenericConnectionLine l in ConnectionLines.Values)
                {
                    l.updatePosition();
                }
            }
            foreach (GenericNode n in ChildNodes)
            {
                // może rzucać wyjątek
                // jeśli ConnectionLine jest nie ustawiony
                if (n.ConnectionLines != null)
                {
                    // update tylko polaczenia z biezacym nodem - jako parentem
                    n.ConnectionLines[this].updatePosition();
                }
            }
        }

        #endregion

        #region - HideConnectionLines -

        /// <summary>
        /// Ukrywa wszystkie linie polaczen
        /// </summary>
        public void HideConnectionLines()
        {
            foreach (GenericConnectionLine l in ConnectionLines.Values)
            {
                l.Visibility = Visibility.Hidden;
            }
            foreach (GenericNode n in ChildNodes)
            {
                if (n.ConnectionLines != null)
                {
                    n.ConnectionLines[this].Visibility = Visibility.Hidden;
                }
            }
        }

        #endregion

        #region - ShowConnectionLines -

        /// <summary>
        /// Ukrywa wszystkie linie polaczen
        /// </summary>
        public void ShowConnectionLines()
        {

            foreach (GenericConnectionLine l in ConnectionLines.Values)
            {
                l.Visibility = Visibility.Visible;
            }
            foreach (GenericNode n in ChildNodes)
            {
                if (n.ConnectionLines != null)
                {
                    n.ConnectionLines[this].Visibility = Visibility.Visible;
                }
            }
        }

        #endregion

        #region - CreateConnectorLineTo -

        public ArrowLine CreateConnectorLineTo(GenericNode to)
        {
            ArrowLine connector = new ArrowLine(this, to, this.Parent as Diagram.DiagramSheet);
            connector.Stroke = Brushes.DarkGray;
            connector.StrokeThickness = 2.0;
            connector.HeadHeight = 4;
            connector.HeadWidth = 12;
            connector.Visibility = Visibility.Visible;
            // Z-index na -100 - aby był na samym spodzie elementów
            connector.SetValue(Canvas.ZIndexProperty, -100);
            to.ConnectionLines.Add(this, connector);

            return connector;
        }

        #endregion

        #region - CreateNoteConnectorLineTo -

        public ConnectionLineForNote CreateNoteConnectorLineTo(GenericNode to)
        {
            ConnectionLineForNote connector = new ConnectionLineForNote(this, to);
            connector.Stroke = Brushes.LightGray;
            connector.StrokeThickness = 1.5;
            connector.Visibility = Visibility.Visible;
            // Z-index na -100 - aby był na samym spodzie elementów
            connector.SetValue(Canvas.ZIndexProperty, -100);
            to.ConnectionLines.Add(this, connector);

            return connector;
        }

        #endregion

        #region - BorderRect_MouseEnter -

        void BorderRect_MouseEnter(object sender, MouseEventArgs e)
        {
            if (!isInResizeMode)
            {
                prevCursor = Cursor;
                Cursor = Cursors.SizeNWSE;
            }
        }

        #endregion

        #region - BorderRect_MouseLeave -

        void BorderRect_MouseLeave(object sender, MouseEventArgs e)
        {
            if (isInResizeMode)
            {
                double w, h;
                w = tmpWidth + Mouse.GetPosition(this).X - mouseDownX;
                h = tmpHeight + Mouse.GetPosition(this).Y - mouseDownY;
                if (w > this.MinWidth)
                {
                    this.Width = w;
                }
                if (h > this.MinHeight)
                {
                    this.Height = h;
                }
            }
            else
            {
                Cursor = prevCursor;
            }
        }

        #endregion

        #region - GenericNode_MouseMove -

        void GenericNode_MouseMove(object sender, MouseEventArgs e)
        {
            DoResize();
        }

        #endregion

        #region - BorderRect_MouseUp -

        void BorderRect_MouseUp(object sender, MouseButtonEventArgs e)
        {
            endResize();
        }

        #endregion

        #region - BorderRect_MouseDown -

        void BorderRect_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (IsSizeable)
                beginResize();
        }

        #endregion

        #region - beginResize -

        void beginResize()
        {
            if (!isInResizeMode)
            {
                this.BorderRect.CaptureMouse();
                isInResizeMode = true;
                mouseDownX = Mouse.GetPosition(this).X;
                mouseDownY = Mouse.GetPosition(this).Y;
                tmpWidth = this.Width;
                tmpHeight = this.Height;                
            }
        }

        #endregion

        #region - DoResize -

        public void DoResize()
        {
            if (isInResizeMode)
            {
                double mx, my;
                mx = Mouse.GetPosition(this).X;
                my = Mouse.GetPosition(this).Y;
                if (mx > 0)
                    this.Width = (tmpWidth + mx - mouseDownX) > 0 ? (tmpWidth + mx - mouseDownX) : 0.0;
                if (my > 0)
                    this.Height = (tmpHeight + my - mouseDownY) > 0 ? tmpHeight + my - mouseDownY : 0.0;
            }
        }

        #endregion

        #region - endResize -

        void endResize()
        {
            if (isInResizeMode)
            {
                this.BorderRect.ReleaseMouseCapture();
                Cursor = prevCursor;
                isInResizeMode = false;
                this.UpdateConnectionLines();
            }
        }

        #endregion

        #region - NodeTextField_LostFocus -

        void NodeTextField_LostFocus(object sender, RoutedEventArgs e)
        {
            this.NodeTextField.Focusable = false;
            this.NodeTextField.Background = Brushes.Transparent;
            this.NodeTextField.BorderThickness = new Thickness(0.0);
            this.NodeTextField.BorderBrush = Brushes.Transparent;
        }

        #endregion

        #region - NodeTextField_GotFocus -

        void NodeTextField_GotFocus(object sender, RoutedEventArgs e)
        {
            this.NodeTextField.Background = Brushes.White;
            this.NodeTextField.BorderThickness = new Thickness(1.0);
            this.NodeTextField.BorderBrush = Brushes.Black;
        }

        #endregion

        #region - NewdecissionNode_MouseDown -

        void NewdecissionNode_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!isDragging)
            {
                isDragging = true;
            }
            if (ChildControlBeginDrag != null)
            {
                ChildControlBeginDrag(this, NODE_TYPE.DECISSION_NODE);
            }
        }

        #endregion

        #region - EndDrag -

        public void EndDrag()
        {
            isDragging = false;
        }

        #endregion

        #region - NewActionNode_MouseDown -

        void NewActionNode_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!isDragging)
            {
                isDragging = true;                
            }
            if (ChildControlBeginDrag != null)
            {
                ChildControlBeginDrag(this, NODE_TYPE.ACTION_NODE);
            }
        }

        #endregion

        #region - EVENT BeginDrag -

        public delegate void BeginDragDelegate(GenericNode sender, NODE_TYPE t);
        public event BeginDragDelegate ChildControlBeginDrag;
        
        #endregion

        #region - EVENT EndDrag -

        public delegate void EndDragDelegate(NODE_TYPE t);
        public event EndDragDelegate ChildControlEndDrag;

        #endregion

        #region - EVENT-y ShapeMouseDown i ShapeMouseUp -

        public event MouseButtonEventHandler ShapeMouseDown;
        public event MouseButtonEventHandler ShapeMouseUp;

        #endregion

        #region - Shape -

        private Shape shape = null;
        public Shape Shape
        {
            get
            {
                return shape;
            }
            set
            {
                shape = value;
                shape.MouseDown += new MouseButtonEventHandler(shape_MouseDown);
                shape.MouseUp += new MouseButtonEventHandler(shape_MouseUp);
            }
        }

        void shape_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (ShapeMouseUp != null)
            {
                ShapeMouseUp(sender, e);
            }            
        }

        void shape_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (ShapeMouseDown != null)
            {
                ShapeMouseDown(sender, e);
            }
        }

        #endregion

        #region Left, Right, Top, Bottom

        public double Left
        {
            get { return (this.GetValue(Canvas.LeftProperty) as double?).Value + ShapeGrid.Margin.Left; }
        }

        public double Right
        {
            get { return Left + this.Width - ShapeGrid.Margin.Right - ShapeGrid.Margin.Left; }
        }

        public double Top
        {
            get { return (this.GetValue(Canvas.TopProperty) as double?).Value + ShapeGrid.Margin.Top; }
        }

        public double Bottom
        {
            get { return Top + this.Height - ShapeGrid.Margin.Bottom - ShapeGrid.Margin.Top; }
        }

        #endregion

        #region ShapeGridWidth, ShapeWidthHeight, MiddleX, MiddleY

        public double ShapeGridWidth { get { return Right - Left; } }
        public double ShapeGridHeight { get { return Bottom - Top; } }
        public double MiddleX { get { return ShapeGridWidth / 2.0 + Left; } }
        public double MiddleY { get { return ShapeGridHeight / 2.0 + Top; } }

        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            
        }

        #endregion
    }

    #region - Typ wyliczeniowy NODE_TYPE -

    public enum NODE_TYPE
    {
        DECISSION_NODE, ACTION_NODE, START_POINT, END_POINT, NOTE_NODE
    };

    #endregion
}
